 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.commands;

 import java.util.ArrayList ;
 import java.util.List ;

 import org.eclipse.core.commands.AbstractParameterValueConverter;
 import org.eclipse.core.commands.Category;
 import org.eclipse.core.commands.Command;
 import org.eclipse.core.commands.ParameterType;
 import org.eclipse.core.commands.State;
 import org.eclipse.core.commands.common.HandleObject;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionDelta;
 import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.core.runtime.IRegistryChangeEvent;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.commands.ICommandService;
 import org.eclipse.ui.internal.WorkbenchMessages;
 import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
 import org.eclipse.ui.internal.services.RegistryPersistence;
 import org.eclipse.ui.internal.util.PrefUtil;

 /**
  * <p>
  * A static class for accessing the registry and the preference store.
  * </p>
  *
  * @since 3.1
  */
 final class CommandPersistence extends RegistryPersistence {

     /**
      * The index of the category elements in the indexed array.
      *
      * @see CommandPersistence#read()
      */
     private static final int INDEX_CATEGORY_DEFINITIONS = 0;

     /**
      * The index of the command elements in the indexed array.
      *
      * @see CommandPersistence#read()
      */
     private static final int INDEX_COMMAND_DEFINITIONS = 1;

     /**
      * The index of the commandParameterType elements in the indexed array.
      *
      * @see CommandPersistence#read()
      * @since 3.2
      */
     private static final int INDEX_PARAMETER_TYPE_DEFINITIONS = 2;

     /**
      * Reads all of the category definitions from the commands extension point.
      *
      * @param configurationElements
      * The configuration elements in the commands extension point;
      * must not be <code>null</code>, but may be empty.
      * @param configurationElementCount
      * The number of configuration elements that are really in the
      * array.
      * @param commandService
      * The command service to which the categories should be added;
      * must not be <code>null</code>.
      */
     private static final void readCategoriesFromRegistry(
             final IConfigurationElement[] configurationElements,
             final int configurationElementCount,
             final ICommandService commandService) {
         // Undefine all the previous handle objects.
 final HandleObject[] handleObjects = commandService
                 .getDefinedCategories();
         if (handleObjects != null) {
             for (int i = 0; i < handleObjects.length; i++) {
                 handleObjects[i].undefine();
             }
         }

         // Define the uncategorized category.
 commandService
                 .defineUncategorizedCategory(
                         WorkbenchMessages.CommandService_AutogeneratedCategoryName,
                         WorkbenchMessages.CommandService_AutogeneratedCategoryDescription);

         final List warningsToLog = new ArrayList (1);

         for (int i = 0; i < configurationElementCount; i++) {
             final IConfigurationElement configurationElement = configurationElements[i];

             // Read out the category identifier.
 final String categoryId = readRequired(configurationElement,
                     ATT_ID, warningsToLog, "Categories need an id"); //$NON-NLS-1$
 if (categoryId == null) {
                 continue;
             }

             // Read out the name.
 final String name = readRequired(configurationElement, ATT_NAME,
                     warningsToLog, "Categories need a name", //$NON-NLS-1$
 categoryId);
             if (name == null) {
                 continue;
             }

             // Read out the description.
 final String description = readOptional(configurationElement,
                     ATT_DESCRIPTION);

             final Category category = commandService.getCategory(categoryId);
             category.define(name, description);
         }

         // If there were any warnings, then log them now.
 logWarnings(
                 warningsToLog,
                 "Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points."); //$NON-NLS-1$
 }

     /**
      * Reads all of the command definitions from the commands extension point.
      *
      * @param configurationElements
      * The configuration elements in the commands extension point;
      * must not be <code>null</code>, but may be empty.
      * @param configurationElementCount
      * The number of configuration elements that are really in the
      * array.
      * @param commandService
      * The command service to which the commands should be added;
      * must not be <code>null</code>.
      */
     private static final void readCommandsFromRegistry(
             final IConfigurationElement[] configurationElements,
             final int configurationElementCount,
             final ICommandService commandService) {
         // Undefine all the previous handle objects.
 final HandleObject[] handleObjects = commandService
                 .getDefinedCommands();
         if (handleObjects != null) {
             for (int i = 0; i < handleObjects.length; i++) {
                 handleObjects[i].undefine();
             }
         }

         final List warningsToLog = new ArrayList (1);

         for (int i = 0; i < configurationElementCount; i++) {
             final IConfigurationElement configurationElement = configurationElements[i];

             // Read out the command identifier.
 final String commandId = readRequired(configurationElement, ATT_ID,
                     warningsToLog, "Commands need an id"); //$NON-NLS-1$
 if (commandId == null) {
                 continue;
             }

             // Read out the name.
 final String name = readRequired(configurationElement, ATT_NAME,
                     warningsToLog, "Commands need a name"); //$NON-NLS-1$
 if (name == null) {
                 continue;
             }

             // Read out the description.
 final String description = readOptional(configurationElement,
                     ATT_DESCRIPTION);

             // Read out the category id.
 String categoryId = configurationElement
                     .getAttribute(ATT_CATEGORY_ID);
             if ((categoryId == null) || (categoryId.length() == 0)) {
                 categoryId = configurationElement.getAttribute(ATT_CATEGORY);
                 if ((categoryId != null) && (categoryId.length() == 0)) {
                     categoryId = null;
                 }
             }

             // Read out the parameters.
 final Parameter[] parameters = readParameters(configurationElement,
                     warningsToLog, commandService);

             // Read out the returnTypeId.
 final String returnTypeId = readOptional(configurationElement,
                     ATT_RETURN_TYPE_ID);

             // Read out the help context identifier.
 final String helpContextId = readOptional(configurationElement,
                     ATT_HELP_CONTEXT_ID);

             final Command command = commandService.getCommand(commandId);
             final Category category = commandService.getCategory(categoryId);
             if (!category.isDefined()) {
                 addWarning(
                         warningsToLog,
                         "Commands should really have a category", //$NON-NLS-1$
 configurationElement, commandId,
                         "categoryId", categoryId); //$NON-NLS-1$
 }

             final ParameterType returnType;
             if (returnTypeId == null) {
                 returnType = null;
             } else {
                 returnType = commandService.getParameterType(returnTypeId);
             }

             command.define(name, description, category, parameters, returnType,
                     helpContextId);
             readState(configurationElement, warningsToLog, command);
         }

         // If there were any warnings, then log them now.
 logWarnings(
                 warningsToLog,
                 "Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points."); //$NON-NLS-1$
 }

     /**
      * Reads the parameters from a parent configuration element. This is used to
      * read the parameter sub-elements from a command element. Each parameter is
      * guaranteed to be valid. If invalid parameters are found, then a warning
      * status will be appended to the <code>warningsToLog</code> list.
      *
      * @param configurationElement
      * The configuration element from which the parameters should be
      * read; must not be <code>null</code>.
      * @param warningsToLog
      * The list of warnings found during parsing. Warnings found
      * while parsing the parameters will be appended to this list.
      * This value must not be <code>null</code>.
      * @param commandService
      * The command service from which the parameter can get parameter
      * types; must not be <code>null</code>.
      * @return The array of parameters found for this configuration element;
      * <code>null</code> if none can be found.
      */
     private static final Parameter[] readParameters(
             final IConfigurationElement configurationElement,
             final List warningsToLog, final ICommandService commandService) {
         final IConfigurationElement[] parameterElements = configurationElement
                 .getChildren(TAG_COMMAND_PARAMETER);
         if ((parameterElements == null) || (parameterElements.length == 0)) {
             return null;
         }

         int insertionIndex = 0;
         Parameter[] parameters = new Parameter[parameterElements.length];
         for (int i = 0; i < parameterElements.length; i++) {
             final IConfigurationElement parameterElement = parameterElements[i];
             // Read out the id
 final String id = readRequired(parameterElement, ATT_ID,
                     warningsToLog, "Parameters need an id"); //$NON-NLS-1$
 if (id == null) {
                 continue;
             }

             // Read out the name.
 final String name = readRequired(parameterElement, ATT_NAME,
                     warningsToLog, "Parameters need a name"); //$NON-NLS-1$
 if (name == null) {
                 continue;
             }

             /*
              * The IParameterValues will be initialized lazily as an
              * IExecutableExtension.
              */

             // Read out the typeId attribute, if present.
 final String typeId = readOptional(parameterElement, ATT_TYPE_ID);

             // Read out the optional attribute, if present.
 final boolean optional = readBoolean(parameterElement,
                     ATT_OPTIONAL, true);

             final ParameterType type;
             if (typeId == null) {
                 type = null;
             } else {
                 type = commandService.getParameterType(typeId);
             }

             final Parameter parameter = new Parameter(id, name,
                     parameterElement, type, optional);
             parameters[insertionIndex++] = parameter;
         }

         if (insertionIndex != parameters.length) {
             final Parameter[] compactedParameters = new Parameter[insertionIndex];
             System.arraycopy(parameters, 0, compactedParameters, 0,
                     insertionIndex);
             parameters = compactedParameters;
         }

         return parameters;
     }

     /**
      * Reads all of the commandParameterType definitions from the commands
      * extension point.
      *
      * @param configurationElements
      * The configuration elements in the commands extension point;
      * must not be <code>null</code>, but may be empty.
      * @param configurationElementCount
      * The number of configuration elements that are really in the
      * array.
      * @param commandService
      * The command service to which the commands should be added;
      * must not be <code>null</code>.
      * @since 3.2
      */
     private static final void readParameterTypesFromRegistry(
             final IConfigurationElement[] configurationElements,
             final int configurationElementCount,
             final ICommandService commandService) {

         // Undefine all the previous handle objects.
 final HandleObject[] handleObjects = commandService
                 .getDefinedParameterTypes();
         if (handleObjects != null) {
             for (int i = 0; i < handleObjects.length; i++) {
                 handleObjects[i].undefine();
             }
         }

         final List warningsToLog = new ArrayList (1);

         for (int i = 0; i < configurationElementCount; i++) {
             final IConfigurationElement configurationElement = configurationElements[i];

             // Read out the commandParameterType identifier.
 final String parameterTypeId = readRequired(configurationElement,
                     ATT_ID, warningsToLog, "Command parameter types need an id"); //$NON-NLS-1$
 if (parameterTypeId == null) {
                 continue;
             }

             // Read out the type.
 final String type = readOptional(configurationElement, ATT_TYPE);

             // Read out the converter.
 final String converter = readOptional(configurationElement,
                     ATT_CONVERTER);

             /*
              * if the converter attribute was given, create a proxy
              * AbstractParameterValueConverter for the ParameterType, otherwise
              * null indicates there is no converter
              */
             final AbstractParameterValueConverter parameterValueConverter = (converter == null) ? null
                     : new ParameterValueConverterProxy(configurationElement);

             final ParameterType parameterType = commandService
                     .getParameterType(parameterTypeId);
             parameterType.define(type, parameterValueConverter);
         }

         // If there were any warnings, then log them now.
 logWarnings(
                 warningsToLog,
                 "Warnings while parsing the commandParameterTypes from the 'org.eclipse.ui.commands' extension point."); //$NON-NLS-1$

     }

     /**
      * Reads the states from a parent configuration element. This is used to
      * read the state sub-elements from a command element. Each state is
      * guaranteed to be valid. If invalid states are found, then a warning
      * status will be appended to the <code>warningsToLog</code> list.
      *
      * @param configurationElement
      * The configuration element from which the states should be
      * read; must not be <code>null</code>.
      * @param warningsToLog
      * The list of warnings found during parsing. Warnings found
      * while parsing the parameters will be appended to this list.
      * This value must not be <code>null</code>.
      * @param command
      * The command for which the state is being read; may be
      * <code>null</code>.
      */
     private static final void readState(
             final IConfigurationElement configurationElement,
             final List warningsToLog, final Command command) {
         final IConfigurationElement[] stateElements = configurationElement
                 .getChildren(TAG_STATE);
         if ((stateElements == null) || (stateElements.length == 0)) {
             return;
         }

         for (int i = 0; i < stateElements.length; i++) {
             final IConfigurationElement stateElement = stateElements[i];

             final String id = readRequired(stateElement, ATT_ID, warningsToLog,
                     "State needs an id"); //$NON-NLS-1$
 if (id == null) {
                 continue;
             }

             if (checkClass(stateElement, warningsToLog,
                     "State must have an associated class", id)) { //$NON-NLS-1$
 final State state = new CommandStateProxy(stateElement,
                         ATT_CLASS, PrefUtil.getInternalPreferenceStore(),
                         CommandService.createPreferenceKey(command, id));
                 command.addState(id, state);
             }
         }
     }

     /**
      * The command service with which this persistence class is associated;
      * never <code>null</code>.
      */
     private final ICommandService commandService;

     /**
      * Constructs a new instance of <code>CommandPersistence</code>.
      *
      * @param commandService
      * The command service which should be populated with the values
      * from the registry; must not be <code>null</code>.
      */
     CommandPersistence(final ICommandService commandService) {
         if (commandService == null) {
             throw new NullPointerException ("The command service cannot be null"); //$NON-NLS-1$
 }
         this.commandService = commandService;
     }

     protected final boolean isChangeImportant(final IRegistryChangeEvent event) {
         final IExtensionDelta[] commandDeltas = event.getExtensionDeltas(
                 PlatformUI.PLUGIN_ID, IWorkbenchRegistryConstants.PL_COMMANDS);
         if (commandDeltas.length == 0) {
             final IExtensionDelta[] actionDefinitionDeltas = event
                     .getExtensionDeltas(PlatformUI.PLUGIN_ID,
                             IWorkbenchRegistryConstants.PL_ACTION_DEFINITIONS);
             if (actionDefinitionDeltas.length == 0) {
                 return false;
             }
         }

         return true;
     }

     /**
      * Reads all of the commands and categories from the registry,
      *
      * @param commandService
      * The command service which should be populated with the values
      * from the registry; must not be <code>null</code>.
      */
     protected final void read() {
         super.read();

         // Create the extension registry mementos.
 final IExtensionRegistry registry = Platform.getExtensionRegistry();
         int commandDefinitionCount = 0;
         int categoryDefinitionCount = 0;
         int parameterTypeDefinitionCount = 0;
         final IConfigurationElement[][] indexedConfigurationElements = new IConfigurationElement[3][];

         // Sort the commands extension point based on element name.
 final IConfigurationElement[] commandsExtensionPoint = registry
                 .getConfigurationElementsFor(EXTENSION_COMMANDS);
         for (int i = 0; i < commandsExtensionPoint.length; i++) {
             final IConfigurationElement configurationElement = commandsExtensionPoint[i];
             final String name = configurationElement.getName();

             // Check if it is a binding definition.
 if (TAG_COMMAND.equals(name)) {
                 addElementToIndexedArray(configurationElement,
                         indexedConfigurationElements,
                         INDEX_COMMAND_DEFINITIONS, commandDefinitionCount++);
             } else if (TAG_CATEGORY.equals(name)) {
                 addElementToIndexedArray(configurationElement,
                         indexedConfigurationElements,
                         INDEX_CATEGORY_DEFINITIONS, categoryDefinitionCount++);
             } else if (TAG_COMMAND_PARAMETER_TYPE.equals(name)) {
                 addElementToIndexedArray(configurationElement,
                         indexedConfigurationElements,
                         INDEX_PARAMETER_TYPE_DEFINITIONS,
                         parameterTypeDefinitionCount++);
             }
         }

         final IConfigurationElement[] actionDefinitionsExtensionPoint = registry
                 .getConfigurationElementsFor(EXTENSION_ACTION_DEFINITIONS);
         for (int i = 0; i < actionDefinitionsExtensionPoint.length; i++) {
             final IConfigurationElement configurationElement = actionDefinitionsExtensionPoint[i];
             final String name = configurationElement.getName();

             if (TAG_ACTION_DEFINITION.equals(name)) {
                 addElementToIndexedArray(configurationElement,
                         indexedConfigurationElements,
                         INDEX_COMMAND_DEFINITIONS, commandDefinitionCount++);
             }
         }

         readCategoriesFromRegistry(
                 indexedConfigurationElements[INDEX_CATEGORY_DEFINITIONS],
                 categoryDefinitionCount, commandService);
         readCommandsFromRegistry(
                 indexedConfigurationElements[INDEX_COMMAND_DEFINITIONS],
                 commandDefinitionCount, commandService);
         readParameterTypesFromRegistry(
                 indexedConfigurationElements[INDEX_PARAMETER_TYPE_DEFINITIONS],
                 parameterTypeDefinitionCount, commandService);

     }
 }

